#include <sys/io.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define PCI_CONFIG_ADDR(bus, dev, fn, reg) (0x80000000 | (bus << 16) | (dev << 11) | (fn << 8) | (reg & ~3))

void usage()
{
  printf("Uage: readpci [-bdfrth]\n   \
-a addr :  specify bar address default 0 \n \
-b bus  :  specify  PCI bus number(default 0) \n   \
-d dev  :  device number(default 0)\n \
-f fn   :  function number(default 0)\n \
-r reg  :  register address(must be multiple of 4, default 0)\n \
-p port :  specify port number default 0 \n \
-v value:  write a integer value into the address\n \
-h      :  print this help text\n");

  exit(-1);
}

int operamem(int bar, int offset, int modvalue, int iswrite)
{
  int i;
  int fd;
  char * mem;
  int value;

  if ((fd = open("/dve/mem", O_RDWR)) < 0) {
    perror("open error");
    return -1;
  }

  mem = mmap(0, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, bar);
  if (mem == MAP_FAILED) {
    perror("mmap error:");
    return -1;
  }

  value = * ((int *)(mem + offset));
  printf("The value at 0x%x is 0x%x\n", bar + offset, value);

  if (iswrite == 1) {
    printf("write value0x%x at 0x%x\n", modvalue, bar + offset);
    memcpy(mem + offset, & modvalue, 4);
    printf("reread the value at 0x%x is 0x%x\n", bar + offset, * ((int *) (mem + offset)));
  }

  munmap(mem, 4096);
  close(fd);

  return 0;
}

int parseint(char * str)
{
  int value = 0;
  char tmpchar;

  while ((tmpchar = * str) != '\0') {
    if (tmpchar >= 'A' && tmpchar <= 'F')
      tmpchar = tmpchar - 'A' + 10;
    else if (tmpchar >= 'a' && tmpchar <= 'f')
      tmpchar = tmpchar - 'a' + 10;
    else
      tmpchar -= '0';

    value = value * 16 + tmpchar;
    str += 1;
  }

  return value;
}

int main(int argc, char ** argv)
{
  unsigned long val = 0;
  char options[] = "a:b:d:f:r:v:p:h";
  int addr = 0, bus = 0, dev = 0, fn = 0, reg = 0, port = 0;
  int opt, value = 0, iswrite = 0, isreadbar = 0;
  int pointaddr;

  if (argc < 3) {
    usage();
    exit(-1);
  }
  while((opt = getopt(argc, argv, options)) != -1) {
    switch(opt) {
    case 'a':
      addr = parseint(optarg);
      break;
    case 'b':
      bus = atoi(optarg);
      break;
    case 'd':
      dev = atoi(optarg);
      break;
    case 'f':
      fn = atoi(optarg);
      break;
    case 'r':
      reg = parseint(optarg);
      break;
    case 'p':
      port = atoi(optarg);
      isreadbar = 1;
      break;
    case 'v':
      value = parseint(optarg);
      iswrite = 1;
      break;
    case 'h':
    default:
      usage();
      break;
    }
  }

  iopl(3);

  if(iswrite == 0) {
    if (isreadbar == 0) {
      int i;
      for (i = 0; i < 256; i++) {
	outl(PCI_CONFIG_ADDR(bus, dev, fn, i), 0xcf8);
	val = inl(0xcfc);
	printf("PCI:bus %d, dev %d func %d reg 0x%x value is 0x%x\n", bus, dev, fn, i, val);
      }
    }   
    else {
      outl(PCI_CONFIG_ADDR(bus, dev, fn, 0x10), 0xcf8);
      val = inl(0xcfc) & 0xfffffff0;
      printf("the base address value is 0x%x\n", val);

      pointaddr = val + port * 0x1000;
      printf("the offset address value is 0x%x\n", pointaddr + addr);
      operamem(pointaddr, addr, 0, 0);
    }
  }
  else {
    outl(PCI_CONFIG_ADDR(bus, dev, fn, 0x10), 0xcf8);
    val = inl(0xcfc) & 0xfffffff0;
    printf("the base address value is 0x%x\n", val);
    operamem(pointaddr, addr, value, 1);
  }
}
